package ControllerLayer.ControlServices.Recognizer;

import ControllerLayer.ControlServices.Normalizer.INormalizer;
import DataLayer.ImageDataContainer.ImageComponentsInfo.CharacterStatistics;
import DataLayer.ImageDataContainer.ImageComponentsInfo.IImageComponentsInfo;
import DataLayer.ImageDataContainer.ImageComponentsInfo.SingleCharImageInfo;
import DataLayer.ImageDataContainer.ImageInfo.IImageInfo;
import DataLayer.KnowledgeBase.IKnowledgeBase;
import DataLayer.KnowledgeBase.KnowledgeBase;
import DataLayer.KnowledgeBase.SingleCharInfos;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.lang.Math;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;

public class MainRecognizer implements IRecognizer {

    /**
     * Recognizes the text on the image.
     * @param charStats - contains the statistics about the characters based on a Markov model.
     * @param imgCompInfo - Contains the information about the components on the image.
     * @param imgInfo - Contains the image information.
     * @param kb - The knowledgebase.
     * @param normalizer - The normalizer component.
     */
    public void recognizeText(CharacterStatistics charStats, IImageComponentsInfo imgCompInfo, IImageInfo imgInfo, KnowledgeBase kb, INormalizer normalizer) {
        ArrayList<SingleCharImageInfo> scInfos = imgCompInfo.getAllCharImageInfo();

        String prev1 = "_"; // the previous characters
        String prev2 = "_";
        //process all the characters
        for (int i = 0; i < scInfos.size(); ++i) {
            if (! scInfos.get(i).ch.equals("_")) //if it isn't a space character
            {
                recognizeSingleChar(scInfos.get(i), imgInfo, kb, prev1, prev2, charStats, normalizer);
                if (i>=1)
                {
                    prev1 = scInfos.get(i-1).ch;
                    prev2 = scInfos.get(i).ch;
                }
            }
            else
            {
                prev1 = "_";
                prev2 = "_";
            }
        }

        imgCompInfo.updateInformation();
    }


    /**
     * Recognizes a single character.
     * @param sCh - Contains information about the actual character.
     * @param imgInfo - Contains the image information.
     * @param kb - The knowledgebase.
     * @param prev1 - The previous character. The character before the previous character.
     * @param prev2 - The previous character. 
     * @param charStats - contains the statistics about the characters based on a Markov model.
     * @param normalizer - The normalizer component.
     */
    private void recognizeSingleChar(SingleCharImageInfo sCh, IImageInfo imgInfo, KnowledgeBase kb, String prev1, String prev2, CharacterStatistics charStats, INormalizer normalizer) {
        BufferedImage preprocImg = imgInfo.getPreprocessedImage();

        //get the image of the actual character
        BufferedImage sample = preprocImg.getSubimage(sCh.y1, sCh.x1, sCh.y2 - sCh.y1 + 1, sCh.x2 - sCh.x1 + 1);
        BufferedImage normSample = normalizer.normalizeSingleChar(sample); //normalize this character
        
        //computes and stores all the distances from all the avg characters
        HashMap<String, Double> distances = new HashMap<String, Double>();
        for (SingleCharInfos sChInfo : kb.charInfoMap.values()) {
            BufferedImage avg = sChInfo.avg.getBufferedImage();
            double actualDistance = getDistance(normSample, avg);
            distances.put(Character.toString(sChInfo.ch), actualDistance);
        }

        //get the best candidate
        String best = chooseBestCandidate(distances, prev1, prev2, charStats);
        ////System.out.println("best candidate: " + best);
        sCh.ch = best;
    }

//    //tesztelésre csak
//    private void recognizeSingleChar(CharacterStatistics charStats) {
//
//        String prev1 = "a";
//        String prev2 = "t";
//
//        HashMap<String, Double> distances = new HashMap<String, Double>();
//        distances.put(Character.toString('x'), 1000.0);
//        distances.put(Character.toString('l'), 5000.0);
//        distances.put(Character.toString('m'), 3000.0);
//        distances.put(Character.toString('e'), 6000.0);
//        distances.put(Character.toString('a'), 3000.0);
//        distances.put(Character.toString('t'), 5000.0);
//        distances.put(Character.toString('s'), 3000.0);
//        distances.put(Character.toString('z'), 1000.0);
//        //get the best candidate
//        String best = chooseBestCandidate(distances, prev1, prev2, charStats);
//        ////System.out.println("best candidate: " + best + " " + distances.get(best));
//    }

    //private kell legyen, csak a teszt miatt
    /**
     * Calculates the distance between the sample(actual) image and the actual averaged character image.
     * @param sample
     * @param avg
     * @return
     */
    public double getDistance(BufferedImage sample, BufferedImage avg) {
        if (avg != null) {
            return Math.sqrt(linearKernel(sample, sample) + linearKernel(avg, avg) - 2 * linearKernel(sample, avg));
        } else {
            return Integer.MAX_VALUE;
        }
    }

    /**
     * Calculates the dot product of the 2 images.
     * @param sample
     * @param avg
     * @return - The dot product.
     */
    private double linearKernel(BufferedImage sample, BufferedImage avg) {
        double s = 0;
        int w = sample.getWidth();
        int h = sample.getHeight();

        for (int i = 0; i < w; ++i) {
            for (int j = 0; j < h; ++j) {
                Color colSmpl = new Color(sample.getRGB(i, j));
                Color colAvg = new Color(avg.getRGB(i, j));
                int red = colSmpl.getRed();
                int green = colSmpl.getGreen();
                int blue = colSmpl.getBlue();
                double c = (red + green + blue) / 3.0;

                red = colAvg.getRed();
                green = colAvg.getGreen();
                blue = colAvg.getBlue();

                c *= (red + green + blue);
                s += c;
            }
        }

        return s;
    }

    /**
     * Chooses the best candidate from the characters, based on the Markov model.
     * @param distances - Contains the distances between the actual and all the avg characters.
     * @param sCh - The actual character information.
     * @param imgCompInfo - The information about the whole image and characters.
     * @return - The best candidate.
     */
    private String chooseBestCandidate(HashMap<String, Double> distances, String prev1, String prev2, CharacterStatistics charStats) {
        LinkedHashMap sortedValues = sortHashMapByValues(distances);
        Iterator iter = sortedValues.keySet().iterator();

        //return (String) iter.next();
        
        if (prev1.equals("_") || prev2.equals("_"))
        {
            return (String) iter.next();
        }
        else
        {
            ArrayList<String> candidates = new ArrayList<String>(); //stores the characters in order
            ArrayList<Double> dists = new ArrayList<Double>(); // stores the distances in order
            ArrayList<Double> probs = new ArrayList<Double>(); // stores the calculated probabilities for each candidate
            for (int i = 0; i<5; ++i)
            {
                String ch = (String) iter.next();
                candidates.add(ch);
                double dist = (Double)sortedValues.get(ch);
                dists.add(dist);
            }

            //calculate each probability
            for (int i = 0; i<5; ++i)
            {
                String ch = candidates.get(i);
                double dist = dists.get(i) *1.0 / dists.get(4); //normalized distance
                double odds = charStats.getOcc(prev1.charAt(0), prev2.charAt(0), ch.charAt(0)); //normalized occurence
                double prob = (1-dist + 0.1) * odds; //the probability
                ////System.out.println("char: " + ch + " - prob: " + prob + " dist: " + dist + " odds: " + odds);
                probs.add(prob);
            }

            //find the maximum probability
            double max = probs.get(0);
            int index = 0;
            for (int i = 1; i<5; ++i)
            {
                if (probs.get(i) > max)
                {
                    max = probs.get(i);
                    index = i;
                }
            }
            return candidates.get(index);
        }
    }

    //PRIVATEEEEEEEE
    /**
     * Sorts the input hashMap by its values.
     * @param passedMap - The input HashMap containing the distances in order.
     * @return A sorted LinkedHashMap.
     */
    public LinkedHashMap sortHashMapByValues(HashMap passedMap) {
        List mapKeys = new ArrayList(passedMap.keySet());
        List mapValues = new ArrayList(passedMap.values());
        Collections.sort(mapValues);
        Collections.sort(mapKeys);

        LinkedHashMap sortedMap = new LinkedHashMap();

        Iterator valueIt = mapValues.iterator();
        while (valueIt.hasNext()) {
            Double val = (Double)valueIt.next();
            Iterator keyIt = mapKeys.iterator();

            while (keyIt.hasNext()) {
                String key = (String)keyIt.next();
                Double comp1 = (Double)passedMap.get(key);

                if (comp1.equals(val)) {
                    mapKeys.remove(key);
                    sortedMap.put((String) key, (Double) val);
                    break;
                }
            }
        }   
        return sortedMap;
    }

//    public static void main(String args[])
//    {
//        MainRecognizer mr = new MainRecognizer();
//        HashMap<String, Double> distances = new HashMap<String, Double>();
//
//        distances.put("z", 5.0);
//        distances.put("a", 50.2);
//        distances.put("t", 15.11);
//        distances.put("b", 7.3);
//        distances.put("x", 53.2);
//        distances.put("s", 5.11);
//        distances.put("w", 17.3);
//
//        LinkedHashMap sortedMap = mr.sortHashMapByValues(distances);
//
//        System.out.println(sortedMap.toString());
//    }

//    //csak tesztnek
//        public static void main(String args[])
//    {
//        MainRecognizer mr = new MainRecognizer();
//
//        CharacterStatistics charStats = new CharacterStatistics();
//        charStats.loadOccMatrix();
//        System.out.println(charStats.getOcc('a', 't', 'h'));
//        mr.recognizeSingleChar(charStats);
//    }
}
